package org.xtlgen;

import java.awt.geom.Point2D;
import java.awt.geom.PathIterator;

import java.io.PrintStream;
import java.io.IOException;
import java.io.File;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.TreeSet;

/**
 * FileCanvas writes the Shape hierarchy to a file.
 */
public class FileCanvas implements Canvas {
  /**
   * Create the file canvas and associate with the given file.
   *
   * @param file File object to use.
   */
  public FileCanvas(File file) {
    this.file = file;
    this.points = new HashMap<Point2D.Double,Integer>();
    this.sortedPoints = new ArrayList<Point2D.Double>();
    this.pointID = 0;
    this.shapes = new ArrayList<Shape>();
  }

  /**
   * Draw the given shape.
   * FileCanvas caches points and shapes here and will do the actual writing
   * when {@link #export} is called. This is due to the CAD system requirements
   * on having unique points.
   *
   * @param shape The Shape to draw.
   */
  public void draw(Shape shape) {
    int segType = PathIterator.SEG_MOVETO;
    double[] coords = new double[6];
    PathIterator pathIt = shape.getAWTShape().getPathIterator(null);

    // Store points
    while ( ! pathIt.isDone() ) {
      segType = pathIt.currentSegment(coords);
      addPoints(segType, coords);
      pathIt.next();
    }

    // Store shapes
    shapes.add(shape);
  }

  /**
   * Export the drawing to the file given as parameter in the constructor.
   *
   * @throws IOException if an I/O error occurs.
   */
  public void export() throws IOException {
    PrintStream out = new PrintStream(this.file);

    // Write out point information
    for (int i=0; i<sortedPoints.size(); i++) {
      Point2D.Double point = sortedPoints.get(i);
      out.format("k,\t%d,%f,%f,0,\n", i, point.getX(), point.getY());
    }

    // Write out shapes
    for (Shape shape : shapes) {
      int segType = PathIterator.SEG_MOVETO;
      double[] coords = new double[6];
      PathIterator pathIt = shape.getAWTShape().getPathIterator(null);

      out.print("\nS,\t");
      
      // Store points
      while ( ! pathIt.isDone() ) {
        segType = pathIt.currentSegment(coords);
        printSeg(out, segType, coords);        
        pathIt.next();
      }
    }
    
    out.close();
  }

  /*
   * Adds the points to a HashMap that associates them with unique IDs.
   */
  private void addPoints(int segType, double[] coords) {
    int pNum = pointsPerSegType(segType);
    for (int i=0; i<pNum; i+=2) {
      Point2D.Double p = new Point2D.Double(coords[i], coords[i+1]);
      if (!points.containsKey(p)) {
        points.put(p, new Integer(pointID));
        pointID++;
        sortedPoints.add(p);
      }
    }
  }

  /*
   * Print the given segment to the given PrintStream.
   */
  private void printSeg(PrintStream out, int segType, double[] coords) {
    String segString = "?";
    
    switch (segType) { 
      case PathIterator.SEG_MOVETO:  segString = "SEG_MOVETO";  break;
      case PathIterator.SEG_LINETO:  segString = "SEG_LINETO";  break;
      case PathIterator.SEG_QUADTO:  segString = "SEG_QUADTO";  break;
      case PathIterator.SEG_CUBICTO: segString = "SEG_CUBICTO"; break;
      case PathIterator.SEG_CLOSE: segString = "SEG_CLOSE"; break;
    }

    out.print(segString);
    out.print(",");

    int pNum = pointsPerSegType(segType);
    for (int i=0; i<pNum; i+=2) {
      Point2D.Double p = new Point2D.Double(coords[i], coords[i+1]);
      out.print(points.get(p));
      out.print(",");
    }
    
  }
    
  /*
   * Return the number of points associated with the given segment type.
   */
  private static int pointsPerSegType(int segType) {
    switch (segType) {
      case PathIterator.SEG_MOVETO: return 1;
      case PathIterator.SEG_LINETO: return 1;
      case PathIterator.SEG_QUADTO: return 2;
      case PathIterator.SEG_CUBICTO: return 3;
    }
    return 0;
  }

  /*
   * Fields.
   */
  private File file;
  private HashMap<Point2D.Double,Integer> points;
  private ArrayList<Point2D.Double> sortedPoints;
  private int pointID;
  private ArrayList<Shape> shapes;

}